/*
 * Copyright 2007 Dan Hodge
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.datajanitor.baja.rest;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URL;
import java.util.Iterator;

import net.datajanitor.baja.data.FileInfo;
import net.datajanitor.baja.data.UserInfo;
import net.datajanitor.baja.spi.BoxConnectionProvider;
import net.datajanitor.baja.spi.BoxSpiException;
import net.datajanitor.baja.spi.BuilderException;
import net.datajanitor.baja.spi.UrlBuilder;
import net.datajanitor.baja.spi.data.AuthInfo;
import net.datajanitor.baja.spi.data.FileInfoTree;
import net.datajanitor.baja.spi.data.FileInfoTreeBuilder;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.FilePartSource;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.PartSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Text;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

class RestBoxConnectionProvider implements BoxConnectionProvider {
    private static final Log logger = LogFactory.getLog(RestBoxConnectionProvider.class);
    private final HttpClient client;
    
    private FileInfo doFileUpload(AuthInfo authInfo, FileInfo parent, PartSource source) throws BoxSpiException {
        FileInfo uploadedFile = null;
        
        try {
            UrlBuilder uploadURLBuilder = new UrlBuilder().setHost("upload.box.net");
            uploadURLBuilder.setPath("api/1.0/upload/" + authInfo.getAuthToken() + "/" + parent.getId());
            URL uploadUrl = uploadURLBuilder.build();

            // TODO: send the correct content-type (and charset) so box.net can display files correctly in the browser
            PostMethod method = new PostMethod(uploadUrl.toString());
            MultipartRequestEntity multiPart = new MultipartRequestEntity(
                    new Part[] { new FilePart("new_file1", source, "application/octet-stream", "ISO-8859-1") },
                    method.getParams());  
            method.setRequestEntity(multiPart);
            
            this.client.executeMethod(method);
            
            SAXBuilder xmlBuilder = new SAXBuilder();
            Document doc = xmlBuilder.build(method.getResponseBodyAsStream());
            
            Element root = doc.getRootElement();
            String status = getTextContent(root.getChild("status"));
            if ("upload_ok".equals(status)) {
                if (logger.isDebugEnabled()) {
                    XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
                    StringWriter writer = new StringWriter();
                    xmlOut.output(doc, writer);

                    logger.debug("Result: " + writer.toString());
                }
                
                Element files = root.getChild("files");
                for (Iterator i = files.getChildren().iterator(); i.hasNext(); ) {
                    Element fileElement = (Element) i.next();
                    uploadedFile = parent.addChild(
                            Integer.parseInt(fileElement.getAttributeValue("id")), 
                            fileElement.getAttributeValue("file_name"), 
                            false);
                }
            } else {
                throw new BoxSpiException("File upload failed, status = " + status);
            }
            
            return uploadedFile;
        } catch (HttpException e) {
            throw new BoxSpiException("Error connecting to HTTP server", e);
        } catch (BuilderException e) {
            throw new BoxSpiException("Error constructing request URL", e);
        } catch (IOException e) {
            throw new BoxSpiException("Error reading HTTP response", e);
        } catch (JDOMException e) {
            throw new BoxSpiException("Error", e);
        }        
    }
    
    protected String getTextContent(Element element) {
        StringBuilder text = new StringBuilder();
        for (Object content : element.getContent()) {
            if (content instanceof Text) {
                text.append(((Text) content).getText());
            }
        }
        
        return text.toString();
    }
    
    protected int getIntContent(Element element) {
        String text = getTextContent(element);
        int ival = 0;
        if (text.length() > 0) {
            ival = Integer.parseInt(text);    
        }
        
        return ival;
    }

    protected long getLongContent(Element element) {
        String text = getTextContent(element);
        long lval = 0;
        if (text.length() > 0) {
            lval = Long.parseLong(text);    
        }
        
        return lval;
    }
    
    protected Document doXMLGet(UrlBuilder builder) throws BoxSpiException {
        try {
            URL requestUrl = builder.build();
            if (logger.isDebugEnabled()) {
                logger.debug("Issuing HTTP GET for URL: " + requestUrl);
            }
            
            HttpMethod method = new GetMethod(requestUrl.toString());
            this.client.executeMethod(method);
            
            SAXBuilder xmlBuilder = new SAXBuilder();
            Document doc = xmlBuilder.build(method.getResponseBodyAsStream());
            
            if (logger.isDebugEnabled()) {
                XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
                StringWriter writer = new StringWriter();
                xmlOut.output(doc, writer);
                
                logger.debug("Result: " + writer.toString());
            }
            
            return doc;
        } catch (HttpException e) {
            throw new BoxSpiException("Error connecting to HTTP server", e);
        } catch (BuilderException e) {
            throw new BoxSpiException("Error constructing request URL", e);
        } catch (IOException e) {
            throw new BoxSpiException("Error reading HTTP response", e);
        } catch (JDOMException e) {
            throw new BoxSpiException("Error parsing XML document", e);
        }        
    }
    
    public RestBoxConnectionProvider() {
        this.client = new HttpClient();
    }

    public AuthInfo authenticate(String ticket, String username, String password) throws BoxSpiException {
        try {
            UrlBuilder authURLBuilder = new UrlBuilder();
            authURLBuilder.setPath("api/1.0/auth/" + ticket);
            URL authUrl = authURLBuilder.build();

            PostMethod method = new PostMethod(authUrl.toString());
            method.addParameter("login", username);
            method.addParameter("password", password);
            method.addParameter("dologin", "1");
            method.addParameter("__login", "1");

            this.client.executeMethod(method);
            // assume success

            UrlBuilder authTokenURLBuilder = new RestUrlBuilder("get_auth_token");
            authTokenURLBuilder.addParam("ticket", ticket);

            Document doc = doXMLGet(authTokenURLBuilder);
            Element root = doc.getRootElement();

            String status = getTextContent(root.getChild("status"));
            if ("get_auth_token_ok".equals(status)) {
                String authToken = getTextContent(root.getChild("auth_token"));
                
                UserInfo userInfo = null;
                Element user = root.getChild("user");
                if (user != null) {
                    userInfo = new UserInfo(
                            getTextContent(user.getChild("login")),
                            getTextContent(user.getChild("email")),
                            getIntContent(user.getChild("access_id")),
                            getIntContent(user.getChild("user_id")),
                            getLongContent(user.getChild("space_amount")),
                            getLongContent(user.getChild("space_used"))
                            );
                }
                
                return new AuthInfo(userInfo, authToken);
            } else {
                throw new BoxSpiException("Authentication failed, status = " + status);
            }
        } catch (HttpException e) {
            throw new BoxSpiException("Error connecting to HTTP server", e);
        } catch (BuilderException e) {
            throw new BoxSpiException("Error constructing request URL", e);
        } catch (IOException e) {
            throw new BoxSpiException("Error reading HTTP response", e);
        }
    }

    // IDEA: the SPI does all of the heavy lifting, but it shells out to a script to
    // handle the actual response parsing. Maybe not...
    // Better IDEA: once all of the methods are implemented, identify the common
    // archetypes and have a couple of implementations that cover all of the calls (with params)
    public String createTicket() throws BoxSpiException {
        Document doc = doXMLGet(new RestUrlBuilder("get_ticket"));

        Element root = doc.getRootElement();
        String status = getTextContent(root.getChild("status"));
        if ("get_ticket_ok".equals(status)) {
            return getTextContent(root.getChild("ticket"));
        } else {
            throw new BoxSpiException("get_ticket status = " + status);
        }
    }
    
    public FileInfoTree getTree(AuthInfo authInfo) throws BoxSpiException {
        UrlBuilder builder = new RestUrlBuilder("get_account_tree");
        builder.addParam("folder_id", "0");
        builder.addParam("params%5B%5D", "nozip");
        authInfo.bindParameter(builder, "auth_token");
        
        Document doc = doXMLGet(builder);
        Element root = doc.getRootElement();
        String status = getTextContent(root.getChild("status"));
        if ("listing_ok".equals(status)) {
            Element tree = root.getChild("tree");
            if (tree == null) {
                throw new BoxSpiException("Error: No tree node in response");
            }
            
            FileInfoTreeBuilder fileTreeBuilder = new FileInfoTreeBuilder();
            return fileTreeBuilder.buildFromXml(tree);
        } else {
            throw new BoxSpiException("get_account_tree status = " + status);
        } 
    }

    public FileInfo uploadFile(AuthInfo authInfo, FileInfo parent, File file) throws BoxSpiException {
        try {
            PartSource source = new FilePartSource(file);
            return doFileUpload(authInfo, parent, source);
        } catch (FileNotFoundException e) {
            throw new BoxSpiException("Error loading file: " + file, e);
        }
    }
        
    public FileInfo uploadFile(AuthInfo authInfo, FileInfo parent, byte[] fileData, String fileName) throws BoxSpiException {
        PartSource source = new ByteArrayPartSource(fileName, fileData);
        return doFileUpload(authInfo, parent, source);
    }

    public InputStream streamFile(AuthInfo authInfo, FileInfo file) throws BoxSpiException {
        try {
            UrlBuilder downloadURLBuilder = new UrlBuilder();
            downloadURLBuilder.setPath("api/1.0/download/" + authInfo.getAuthToken() + "/" + file.getId());
            URL downloadUrl = downloadURLBuilder.build();
            
            HttpMethod method = new GetMethod(downloadUrl.toString());
            this.client.executeMethod(method);
            
            return method.getResponseBodyAsStream();
        } catch (HttpException e) {
            throw new BoxSpiException("Error connecting to HTTP server", e);
        } catch (BuilderException e) {
            throw new BoxSpiException("Error constructing request URL", e);
        } catch (IOException e) {
            throw new BoxSpiException("Error reading HTTP response", e);
        }
    }
}